﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using System.Threading;

namespace CloudUpload
{
    public class RESTHelper
    {
        protected bool IsTableStorage { get; set; }

        private string endpoint;
        public string Endpoint
        {
            get
            {
                return endpoint;
            }
            internal set
            {
                endpoint = value;
            }
        }

        private string storageAccount;
        public string StorageAccount
        {
            get
            {
                return storageAccount;
            }
            internal set
            {
                storageAccount = value;
            }
        }

        private string storageKey;
        public string StorageKey
        {
            get
            {
                return storageKey;
            }
            internal set
            {
                storageKey = value;
            }
        }

        private string signature;
        public string Signature
        {
            get
            {
                return signature;
            }
            internal set
            {
                signature = value;
            }
        }


        public RESTHelper(string endpoint, string storageAccount, string storageKey)
        {
            this.Endpoint = endpoint;
            this.StorageAccount = storageAccount;
            this.StorageKey = storageKey;
        }

        public RESTHelper(string endpoint, string signature)
        {
            this.Endpoint = endpoint;
            this.Signature = signature;
            this.StorageKey = "";
            this.StorageAccount = "";
        }

        #region REST HTTP Request Helper Methods

        // Construct and issue a REST request and return the response.

        public HttpWebRequest CreateRESTRequest(string method, string resource, string requestBody = null, SortedList<string, string> headers = null,
            string ifMatch = "", string md5 = "")
        {
            byte[] byteArray = null;
            DateTime now = DateTime.UtcNow;
            string uri = Endpoint + resource;

            if (Signature.Length > 0)
            {
                if (uri.IndexOf('?') > 0)
                {
                    uri += "&" + Signature;
                }
                else
                {
                    uri += "?" + Signature;
                }
            }

            HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
            request.Method = method;
            request.ContentLength = 0;
            request.Headers.Add("x-ms-date", now.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
            request.Headers.Add("x-ms-version", "2009-09-19");

            request.Proxy = WebRequest.GetSystemWebProxy();

            if (IsTableStorage)
            {
                request.ContentType = "application/atom+xml";

                request.Headers.Add("DataServiceVersion", "1.0;NetFx");
                request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx");
            }

            if (headers != null)
            {
                foreach (KeyValuePair<string, string> header in headers)
                {
                    request.Headers.Add(header.Key, header.Value);
                }
            }

            if (!String.IsNullOrEmpty(requestBody))
            {
                request.Headers.Add("Accept-Charset", "UTF-8");

                byteArray = Encoding.UTF8.GetBytes(requestBody);
                request.ContentLength = byteArray.Length;
            }

            if (StorageKey.Length > 0)
            {
                request.Headers.Add("Authorization", AuthorizationHeader(method, now, request, ifMatch, md5));
            }

            if (!String.IsNullOrEmpty(requestBody))
            {
                request.GetRequestStream().Write(byteArray, 0, byteArray.Length);
            }

            return request;
        }

        public HttpWebRequest CreateRESTRequest(string method, string resource, byte[] requestBody, SortedList<string, string> headers = null,
            string ifMatch = "", string md5 = "")
        {
            byte[] byteArray = null;
            DateTime now = DateTime.UtcNow;
            string uri = Endpoint + resource;

            if (Signature.Length > 0)
            {
                uri += "&" + Signature;
            }

            HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
            request.Method = method;
            request.ContentLength = 0;
            request.Headers.Add("x-ms-date", now.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
            request.Headers.Add("x-ms-version", "2009-09-19");

            request.Proxy = WebRequest.GetSystemWebProxy();

            IWebProxy wp = request.Proxy;

            if (IsTableStorage)
            {
                request.ContentType = "application/atom+xml";

                request.Headers.Add("DataServiceVersion", "1.0;NetFx");
                request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx");
            }

            if (headers != null)
            {
                foreach (KeyValuePair<string, string> header in headers)
                {
                    request.Headers.Add(header.Key, header.Value);
                }
            }

            byteArray = requestBody;
            request.ContentLength = byteArray.Length;
            
            if (StorageKey.Length > 0)
            {
                request.Headers.Add("Authorization", AuthorizationHeader(method, now, request, ifMatch, md5));
            }

            request.GetRequestStream().Write(byteArray, 0, byteArray.Length);
            
            return request;
        }

        public HttpWebRequest CreateRESTRequest(string method, string resource, Stream source, SortedList<string, string> headers = null)
        {
            string uri = Endpoint + resource;

            if (Signature.Length > 0)
            {
                uri += "&" + Signature;
            }

            HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
            request.Method = method;
            request.ContentLength = source.Length;

            request.Proxy = WebRequest.GetSystemWebProxy();

            Stream destination = request.GetRequestStream();

            long bsize = 0x4000;
            int bufferSize = (int)Math.Min(bsize, source.Length);
            byte[] buffer = new byte[bufferSize];

            int num;

            while ((num = source.Read(buffer, 0, buffer.Length)) != 0)
            {
                destination.Write(buffer, 0, num);
            }

            return request;
        }

        // Generate an authorization header.

        public string AuthorizationHeader(string method, DateTime now, HttpWebRequest request, string ifMatch = "", string md5 = "")
        {
            string MessageSignature;

            if (IsTableStorage)
            {
                MessageSignature = String.Format("{0}\n\n{1}\n{2}\n{3}",
                    method,
                    "application/atom+xml",
                    now.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
                    GetCanonicalizedResource(request.RequestUri, StorageAccount)
                    );
            }
            else
            {
                MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
                    method,
                    (method == "GET" || method == "HEAD") ? String.Empty : request.ContentLength.ToString(),
                    ifMatch,
                    GetCanonicalizedHeaders(request),
                    GetCanonicalizedResource(request.RequestUri, StorageAccount),
                    md5
                    );
            }
            byte[] SignatureBytes = System.Text.Encoding.UTF8.GetBytes(MessageSignature);
            System.Security.Cryptography.HMACSHA256 SHA256 = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(StorageKey));
            String AuthorizationHeader = "SharedKey " + StorageAccount + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
            return AuthorizationHeader;
        }

        // Get canonicalized headers.

        public string GetCanonicalizedHeaders(HttpWebRequest request)
        {
            ArrayList headerNameList = new ArrayList();
            StringBuilder sb = new StringBuilder();
            foreach (string headerName in request.Headers.Keys)
            {
                if (headerName.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal))
                {
                    headerNameList.Add(headerName.ToLowerInvariant());
                }
            }
            headerNameList.Sort();
            foreach (string headerName in headerNameList)
            {
                StringBuilder builder = new StringBuilder(headerName);
                string separator = ":";
                foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
                {
                    string trimmedValue = headerValue.Replace("\r\n", String.Empty);
                    builder.Append(separator);
                    builder.Append(trimmedValue);
                    separator = ",";
                }
                sb.Append(builder.ToString());
                sb.Append("\n");
            }
            return sb.ToString();
        }

        // Get header values.

        public ArrayList GetHeaderValues(NameValueCollection headers, string headerName)
        {
            ArrayList list = new ArrayList();
            string[] values = headers.GetValues(headerName);
            if (values != null)
            {
                foreach (string str in values)
                {
                    list.Add(str.TrimStart(null));
                }
            }
            return list;
        }

        // Get canonicalized resource.

        public string GetCanonicalizedResource(Uri address, string accountName)
        {
            StringBuilder str = new StringBuilder();
            StringBuilder builder = new StringBuilder("/");
            builder.Append(accountName);
            builder.Append(address.AbsolutePath);
            str.Append(builder.ToString());
            NameValueCollection values2 = new NameValueCollection();
            if (!IsTableStorage)
            {
                NameValueCollection values = HttpUtility.ParseQueryString(address.Query);
                foreach (string str2 in values.Keys)
                {
                    ArrayList list = new ArrayList(values.GetValues(str2));
                    list.Sort();
                    StringBuilder builder2 = new StringBuilder();
                    foreach (object obj2 in list)
                    {
                        if (builder2.Length > 0)
                        {
                            builder2.Append(",");
                        }
                        builder2.Append(obj2.ToString());
                    }
                    values2.Add((str2 == null) ? str2 : str2.ToLowerInvariant(), builder2.ToString());
                }
            }
            ArrayList list2 = new ArrayList(values2.AllKeys);
            list2.Sort();
            foreach (string str3 in list2)
            {
                StringBuilder builder3 = new StringBuilder(string.Empty);
                builder3.Append(str3);
                builder3.Append(":");
                builder3.Append(values2[str3]);
                str.Append("\n");
                str.Append(builder3.ToString());
            }
            return str.ToString();
        }

        #endregion

        #region Retry Delegate

        public delegate T RetryDelegate<T>();
        public delegate void RetryDelegate();

        const int retryCount = 3;
        const int retryIntervalMS = 200;

        // Retry delegate with default retry settings.

        public static T Retry<T>(RetryDelegate<T> del)
        {
            return Retry<T>(del, retryCount, retryIntervalMS);
        }

        // Retry delegate.

        public static T Retry<T>(RetryDelegate<T> del, int numberOfRetries, int msPause)
        {
            int counter = 0;
        RetryLabel:

            try
            {
                counter++;
                return del.Invoke();
            }
            catch (Exception ex)
            {
                if (counter > numberOfRetries)
                {
                    throw ex;
                }
                else
                {
                    if (msPause > 0)
                    {
                        Thread.Sleep(msPause);
                    }
                    goto RetryLabel;
                }
            }
        }


        // Retry delegate with default retry settings.

        public static bool Retry(RetryDelegate del)
        {
            return Retry(del, retryCount, retryIntervalMS);
        }


        public static bool Retry(RetryDelegate del, int numberOfRetries, int msPause)
        {
            int counter = 0;

        RetryLabel:
            try
            {
                counter++;
                del.Invoke();
                return true;
            }
            catch (Exception ex)
            {
                if (counter > numberOfRetries)
                {
                    throw ex;
                }
                else
                {
                    if (msPause > 0)
                    {
                        Thread.Sleep(msPause);
                    }
                    goto RetryLabel;
                }
            }
        }

        #endregion
    }

    public enum ListBlobOptions { All, Blobs, Folders };
    
    public class BlobHelper : RESTHelper
    {
        // Constructor.

        public BlobHelper(string Protocol,string storageAccount, string storageKey)
            : base(Protocol+ "://" + storageAccount + ".blob.core.windows.net/", storageAccount, storageKey)
        {
        }

        public BlobHelper(string Endpoint, string Signature)
            : base(Endpoint, Signature)
        {
        }


        // List containers.
        // Return true on success, false if not found, throw exception on error.

        public List<string> ListContainers()
        {
            return Retry<List<string>>(delegate()
            {
                HttpWebResponse response;

                List<string> containers = new List<string>();

                try
                {
                    response = CreateRESTRequest("GET", "?comp=list").GetResponse() as HttpWebResponse;

                    if ((int)response.StatusCode == 200)
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            string result = reader.ReadToEnd();

                            XElement x = XElement.Parse(result);
                            foreach (XElement container in x.Element("Containers").Elements("Container"))
                            {
                                containers.Add(container.Element("Name").Value);
                            }
                        }
                    }

                    response.Close();

                    return containers;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return null;

                    throw;
                }
            });
        }


        // Create a blob container. 
        // Return true on success, false if already exists, throw exception on error.

        public bool CreateContainer(string container)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    response = CreateRESTRequest("PUT", container + "?restype=container").GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }


        // Get container properties.
        // Return true on success, false if not found, throw exception on error.
        // TODO: modify for retries.

        public bool GetContainerProperties(string container, out string eTag, out string lastModified)
        {
            HttpWebResponse response;

            eTag = String.Empty;
            lastModified = String.Empty;

            try
            {
                response = CreateRESTRequest("HEAD", container + "?restype=container").GetResponse() as HttpWebResponse;
                response.Close();

                if ((int)response.StatusCode == 200)
                {
                    if (response.Headers != null)
                    {
                        eTag = response.Headers["ETag"];
                        lastModified = response.Headers["LastModifiedUtc"];
                    }
                }

                return true;
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError &&
                    ex.Response != null &&
                    (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                    return false;

                throw;
            }
        }


        // Get container metadata.
        // Return true on success, false if not found, throw exception on error.

        public SortedList<string, string> GetContainerMetadata(string container)
        {
            return Retry<SortedList<string, string>>(delegate()
            {
                HttpWebResponse response;

                SortedList<string, string> metadataList = new SortedList<string, string>();

                try
                {
                    response = CreateRESTRequest("HEAD", container + "?restype=container&comp=metadata", string.Empty, metadataList).GetResponse() as HttpWebResponse;
                    response.Close();

                    if ((int)response.StatusCode == 200)
                    {
                        if (response.Headers != null)
                        {
                            for (int i = 0; i < response.Headers.Count; i++)
                            {
                                if (response.Headers.Keys[i].StartsWith("x-ms-meta-"))
                                {
                                    metadataList.Add(response.Headers.Keys[i], response.Headers[i]);
                                }
                            }
                        }
                    }

                    return metadataList;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return null;

                    throw;
                }
            });
        }


        // Set container metadata.
        // Return true on success, false if not found, throw exception on error.

        public bool SetContainerMetadata(string container, SortedList<string, string> metadataList)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();

                    if (metadataList != null)
                    {
                        foreach (KeyValuePair<string, string> value in metadataList)
                        {
                            headers.Add("x-ms-meta-" + value.Key, value.Value);
                        }
                    }

                    response = CreateRESTRequest("PUT", container + "?restype=container&comp=metadata", string.Empty, headers).GetResponse() as HttpWebResponse;
                    response.Close();

                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // Get container access control.
        // Return true on success, false if not found, throw exception on error.
        // accessLevel set to container|blob|private.

        public string GetContainerACL(string container)
        {
            return Retry<string>(delegate()
            {
                HttpWebResponse response;

                string accessLevel = String.Empty;

                try
                {
                    response = CreateRESTRequest("GET", container + "?restype=container&comp=acl").GetResponse() as HttpWebResponse;
                    response.Close();

                    if ((int)response.StatusCode == 200)
                    {
                        if (response.Headers != null)
                        {
                            string access = response.Headers["x-ms-blob-public-access"];
                            if (access != null)
                            {
                                switch (access)
                                {
                                    case "container":
                                    case "blob":
                                        accessLevel = access;
                                        break;
                                    case "true":
                                        accessLevel = "container";
                                        break;
                                    default:
                                        accessLevel = "private";
                                        break;
                                }
                            }
                            else
                            {
                                accessLevel = "private";
                            }
                        }
                    }

                    return accessLevel;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return null;

                    throw;
                }
            });
        }


        // Set container access control.
        // Return true on success, false if not found, throw exception on error. 
        // Set accessLevel to container|blob|private.

        public bool SetContainerACL(string container, string accessLevel)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    switch (accessLevel)
                    {
                        case "container":
                        case "blob":
                            headers.Add("x-ms-blob-public-access", accessLevel);
                            break;
                        case "private":
                        default:
                            break;
                    }

                    response = CreateRESTRequest("PUT", container + "?restype=container&comp=acl", string.Empty, headers).GetResponse() as HttpWebResponse;
                    response.Close();

                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // Get container access policy.
        // Return true on success, false if not found, throw exception on error. 

        public string GetContainerAccessPolicy(string container)
        {
            return Retry<string>(delegate()
            {
                HttpWebResponse response;

                string accessPolicyXml = String.Empty;

                try
                {
                    response = CreateRESTRequest("GET", container + "?restype=container&comp=acl").GetResponse() as HttpWebResponse;

                    if ((int)response.StatusCode == 200)
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            accessPolicyXml = reader.ReadToEnd();
                        }
                    }

                    response.Close();

                    return accessPolicyXml;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return null;

                    throw;
                }
            });
        }


        // Set container access policy (container|blob|private).
        // Return true on success, false if not found, throw exception on error.

        public bool SetContainerAccessPolicy(string container, string accessLevel, string accessPolicyXml)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    switch (accessLevel)
                    {
                        case "container":
                        case "blob":
                            headers.Add("x-ms-blob-public-access", accessLevel);
                            break;
                        case "private":
                        default:
                            break;
                    }

                    response = CreateRESTRequest("PUT", container + "?restype=container&comp=acl", accessPolicyXml, headers).GetResponse() as HttpWebResponse;
                    response.Close();

                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // Delete a blob container. 
        // Return true on success, false if not found, throw exception on error.

        public bool DeleteContainer(string container)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    response = CreateRESTRequest("DELETE", container).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // List blobs in a container.
        // Return true on success, false if not found, throw exception on error.

        public List<string> ListBlobs(string container, string subfolder, ListBlobOptions lbo = ListBlobOptions.All)
        {
            return Retry<List<string>>(delegate()
            {
                HttpWebResponse response;

                List<string> blobs = new List<string>();

                try
                {
                    if (lbo == ListBlobOptions.All)
                    {
                        response = CreateRESTRequest("GET", container + "?restype=container&comp=list&include=snapshots&include=metadata").GetResponse() as HttpWebResponse;
                    }
                    else 
                    {
                        string reqpart = "?restype=container&comp=list&delimiter=%2f";
                        string urlsubfolder = HttpUtility.UrlPathEncode(subfolder);

                        if (urlsubfolder.Length > 0)
                        {
                            reqpart += "&prefix=" + urlsubfolder;
                        }

                        response = CreateRESTRequest("GET", container + reqpart).GetResponse() as HttpWebResponse;
                    }

                    if ((int)response.StatusCode == 200)
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            string result = reader.ReadToEnd();

                            XElement x = XElement.Parse(result);

                            if (lbo == ListBlobOptions.All || lbo == ListBlobOptions.Blobs)
                            {
                                foreach (XElement blob in x.Element("Blobs").Elements("Blob"))
                                {
                                    blobs.Add(blob.Element("Name").Value);
                                }
                            }
                            else
                            {
                                foreach (XElement blob in x.Element("Blobs").Elements("BlobPrefix"))
                                {
                                    blobs.Add(blob.Element("Name").Value);
                                }
                            }
                        }
                    }

                    response.Close();

                    return blobs;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return null;

                    throw;
                }
            });
        }


        // Retrieve the content of a blob. 
        // Return true on success, false if not found, throw exception on error.

        public string GetBlob(string container, string blob)
        {
            return Retry<string>(delegate()
            {
                WebResponse response;

                string content = null;

                try
                {
                    response = CreateRESTRequest("GET", container + "/" + blob).GetResponse() as HttpWebResponse;

                    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                    {
                        content = reader.ReadToEnd();
                    }

                    response.Close();
                    return content;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return null;

                    throw;
                }
            });
        }

        public bool GetBlob(string container, string blob, Stream target)
        {
            return Retry<bool>(delegate()
            {
                WebResponse response;

                try
                {
                    response = CreateRESTRequest("GET", container + "/" + blob).GetResponse() as HttpWebResponse;

                    Stream reader = response.GetResponseStream();
                    
                    long bsize = 0x8000;
                    byte[] buffer = new byte[bsize];

                    int num;

                    while ((num = reader.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        target.Write(buffer, 0, num);
                    }

                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }


        // Create or update a blob. 
        // Return true on success, false if not found, throw exception on error.

        public bool PutBlob(string container, string blob, string content)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-blob-type", "BlockBlob");

                    response = CreateRESTRequest("PUT", container + "/" + blob, content, headers).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }

        public bool PutBlob(string container, string blob, Stream content)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                string requete = String.Format("{0}{1}/{2}?{3}",
                                Endpoint,
                                container,
                                blob,
                                Signature);

                HttpWebRequest request = HttpWebRequest.Create(requete) as HttpWebRequest;
                request.Method = "PUT";
                request.ContentLength = content.Length;
                Stream cloudstream = request.GetRequestStream();

                int bufferSize = (int)Math.Min(0x4000,content.Length);
                int num;
                long wtotal = 0;
                byte[] buffer = new byte[bufferSize];

                while ((num = content.Read(buffer, 0, buffer.Length)) != 0)
                {
                    cloudstream.Write(buffer, 0, num);
                    wtotal += num;    
                }

                try
                {
                    response = request.GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }

                /*
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    //headers.Add("x-ms-blob-type", "BlockBlob");

                    blob = HttpUtility.UrlPathEncode(blob);

                    response = CreateRESTRequest("PUT", container + "/" + blob, content, headers).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }*/
            });
        }


        // Create or update a page blob. 
        // Return true on success, false if not found, throw exception on error.

        public bool PutBlob(string container, string blob, int pageBlobSize)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-blob-type", "PageBlob");
                    headers.Add("x-ms-blob-content-length", pageBlobSize.ToString());

                    response = CreateRESTRequest("PUT", container + "/" + blob, "", headers).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }


        // Create or update a blob condition based on an expected ETag value.
        // Return true on success, false if not found, throw exception on error.

        public bool PutBlobIfUnchanged(string container, string blob, string content, string expectedETagValue)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-blob-type", "BlockBlob");
                    headers.Add("If-Match", expectedETagValue);

                    response = CreateRESTRequest("PUT", container + "/" + blob, content, headers, expectedETagValue).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        ((int)(ex.Response as HttpWebResponse).StatusCode == 409 ||
                        (int)(ex.Response as HttpWebResponse).StatusCode == 412))
                        return false;

                    throw;
                }
            });
        }


        // Create or update a blob with an MD5 hash.
        // Return true on success, false if not found, throw exception on error.

        public bool PutBlobWithMD5(string container, string blob, string content)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    string md5 = Convert.ToBase64String(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(System.Text.Encoding.Default.GetBytes(content)));

                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-blob-type", "BlockBlob");
                    headers.Add("Content-MD5", md5);

                    response = CreateRESTRequest("PUT", container + "/" + blob, content, headers, String.Empty, md5).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        ((int)(ex.Response as HttpWebResponse).StatusCode == 409 ||
                        (int)(ex.Response as HttpWebResponse).StatusCode == 400))
                        return false;

                    throw;
                }
            });
        }


        // Retrieve a page from a block blob. 
        // Return true on success, false if not found, throw exception on error.

        public string GetPage(string container, string blob, int pageOffset, int pageSize)
        {
            return Retry<string>(delegate()
            {
                HttpWebResponse response;

                string content = null;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-range", "bytes=" + pageOffset.ToString() + "-" + (pageOffset + pageSize - 1).ToString());
                    response = CreateRESTRequest("GET", container + "/" + blob, "", headers).GetResponse() as HttpWebResponse;

                    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                    {
                        content = reader.ReadToEnd();
                    }

                    response.Close();
                    return content;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return null;

                    throw;
                }
            });
        }


        // Write a page to a page blob.
        // Return true on success, false if not found, throw exception on error.

        public bool PutPage(string container, string blob, string content, int pageOffset, int pageSize)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-page-write", "update");
                    headers.Add("x-ms-range", "bytes=" + pageOffset.ToString() + "-" + (pageOffset + pageSize - 1).ToString());

                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=page ", content, headers).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }


        // Retrieve the list of regions in use for a page blob. 
        // Return true on success, false if not found, throw exception on error.

        public string[] GetPageRegions(string container, string blob)
        {
            return Retry<string[]>(delegate()
            {
                HttpWebResponse response;

                string[] regions = null;

                try
                {
                    response = CreateRESTRequest("GET", container + "/" + blob + "?comp=pagelist").GetResponse() as HttpWebResponse;

                    if ((int)response.StatusCode == 200)
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            List<string> regionList = new List<string>();
                            string result = reader.ReadToEnd();

                            XElement xml = XElement.Parse(result);

                            foreach (XElement range in xml.Elements("PageRange"))
                            {
                                regionList.Add(range.ToString());
                            }

                            regions = new string[regionList.Count];

                            int i = 0;
                            foreach (string region in regionList)
                            {
                                regions[i++] = region;
                            }
                        }
                    }

                    response.Close();
                    return regions;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return null;

                    throw;
                }
            });
        }


        // Copy a blob. 
        // Return true on success, false if not found, throw exception on error.

        public bool CopyBlob(string sourceContainer, string sourceBlob, string destContainer, string destBlob)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-copy-source", "/" + StorageAccount + "/" + sourceContainer + "/" + sourceBlob);

                    response = CreateRESTRequest("PUT", destContainer + "/" + destBlob, "", headers).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // Retrieve the list of uploaded blocks for a blob. 
        // Return true on success, false if not found, throw exception on error.

        public string[] GetBlockList(string container, string blob)
        {
            return Retry<string[]>(delegate()
            {
                HttpWebResponse response;

                string[] blockIds = null;

                try
                {
                    response = CreateRESTRequest("GET", container + "/" + blob + "?comp=blocklist").GetResponse() as HttpWebResponse;

                    if ((int)response.StatusCode == 200)
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            List<string> blockIdList = new List<string>();
                            string result = reader.ReadToEnd();

                            XElement xml = XElement.Parse(result);

                            foreach (XElement blockGroup in xml.Elements())
                            {
                                foreach (XElement block in blockGroup.Elements("Block"))
                                {
                                    blockIdList.Add(block.Element("Name").Value);
                                }
                            }

                            blockIds = new string[blockIdList.Count];

                            int i = 0;
                            foreach (string blockId in blockIdList)
                            {
                                blockIds[i++] = blockId;
                            }
                        }
                    }

                    response.Close();
                    return blockIds;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return null;

                    throw;
                }
            });
        }


        // Put block - upload a block (portion) of a blob. 
        // Return true on success, false if not found, throw exception on error.

        public bool PutBlock(string container, string blob, int blockId, string[] blockIds, string content)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();

                    byte[] blockIdBytes = BitConverter.GetBytes(blockId);
                    string blockIdBase64 = Convert.ToBase64String(blockIdBytes);

                    blockIds[blockId] = blockIdBase64;

                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=block&blockid=" + blockIdBase64, content, headers).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }

        public bool PutBlock(string container, string blob, int blockId, string[] blockIds, byte[] content)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();

                    string blockIdBase64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")));
                    blockIds[blockId] = blockIdBase64;

                    if (blob[0] == '/')
                    {
                        blob = blob.Substring(1);
                    }

                    blob = HttpUtility.UrlPathEncode(blob);

                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=block&blockid=" + blockIdBase64, content, headers).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }


        // Put block list - complete creation of blob based on uploaded content.
        // Return true on success, false if not found, throw exception on error.

        public bool PutBlockList(string container, string blob, string[] blockIds)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    StringBuilder content = new StringBuilder();
                    content.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
                    content.Append("<BlockList>");
                    for (int i = 0; i < blockIds.Length; i++)
                    {
                        content.Append("<Latest>" + blockIds[i] + "</Latest>");
                    }
                    content.Append("</BlockList>");

                    if (blob[0] == '/')
                    {
                        blob = blob.Substring(1);
                    }

                    blob = HttpUtility.UrlPathEncode(blob);

                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=blocklist", content.ToString(), null).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return false;

                    throw;
                }
            });
        }


        // Create a snapshot of a blob. 
        // Return true on success, false if not found, throw exception on error.

        public bool SnapshotBlob(string container, string blob)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=snapshot").GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // Delete a blob. 
        // Return true on success, false if not found, throw exception on error.

        public bool DeleteBlob(string container, string blob)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    response = CreateRESTRequest("DELETE", container + "/" + blob).GetResponse() as HttpWebResponse;
                    response.Close();
                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // Lease a blob.
        // Lease action: acquire|renew|break|release.
        // Lease Id: returned on acquire action; must be specified for all other actions.
        // Return true on success, false if not found, throw exception on error.

        public string LeaseBlob(string container, string blob, string leaseAction, string leaseId)
        {
            return Retry<string>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();
                    headers.Add("x-ms-lease-action", leaseAction);
                    if (!String.IsNullOrEmpty(leaseId))
                    {
                        headers.Add("x-ms-lease-id", leaseId);
                    }

                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=lease", "", headers).GetResponse() as HttpWebResponse;
                    response.Close();

                    if (leaseAction == "acquire")
                    {
                        leaseId = response.Headers["x-ms-lease-id"];
                    }

                    return leaseId;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 409)
                        return null;

                    throw;
                }
            });
        }


        // Retrieve a blob's properties.
        // Return true on success, false if not found, throw exception on error.

        public SortedList<string, string> GetBlobProperties(string container, string blob)
        {
            return Retry<SortedList<string, string>>(delegate()
            {
                HttpWebResponse response;

                SortedList<string, string> propertiesList = new SortedList<string, string>();

                try
                {
                    response = CreateRESTRequest("HEAD", container + "/" + blob).GetResponse() as HttpWebResponse;
                    response.Close();

                    if ((int)response.StatusCode == 200)
                    {
                        if (response.Headers != null)
                        {
                            for (int i = 0; i < response.Headers.Count; i++)
                            {
                                propertiesList.Add(response.Headers.Keys[i], response.Headers[i]);
                            }
                        }
                    }

                    return propertiesList;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return null;

                    throw;
                }
            });
        }


        // Set blob properties.
        // Return true on success, false if not found, throw exception on error.

        public bool SetBlobProperties(string container, string blob, SortedList<string, string> propertyList)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();

                    if (propertyList != null)
                    {
                        foreach (KeyValuePair<string, string> value in propertyList)
                        {
                            headers.Add(value.Key, value.Value);
                        }
                    }

                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=properties", string.Empty, headers).GetResponse() as HttpWebResponse;
                    response.Close();

                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }


        // Retrieve a blob's metadata.
        // Return true on success, false if not found, throw exception on error.

        public SortedList<string, string> GetBlobMetadata(string container, string blob)
        {
            return Retry<SortedList<string, string>>(delegate()
            {
                HttpWebResponse response;

                SortedList<string, string> metadata = new SortedList<string, string>();

                try
                {
                    response = CreateRESTRequest("HEAD", container + "/" + blob + "?comp=metadata").GetResponse() as HttpWebResponse;
                    response.Close();

                    if ((int)response.StatusCode == 200)
                    {
                        if (response.Headers != null)
                        {
                            for (int i = 0; i < response.Headers.Count; i++)
                            {
                                if (response.Headers.Keys[i].StartsWith("x-ms-meta-"))
                                {
                                    metadata.Add(response.Headers.Keys[i], response.Headers[i]);
                                }
                            }
                        }
                    }

                    return metadata;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return null;

                    throw;
                }
            });
        }


        // Set blob metadata.
        // Return true on success, false if not found, throw exception on error.

        public bool SetBlobMetadata(string container, string blob, SortedList<string, string> metadataList)
        {
            return Retry<bool>(delegate()
            {
                HttpWebResponse response;

                try
                {
                    SortedList<string, string> headers = new SortedList<string, string>();

                    if (metadataList != null)
                    {
                        foreach (KeyValuePair<string, string> value in metadataList)
                        {
                            headers.Add("x-ms-meta-" + value.Key, value.Value);
                        }
                    }

                    response = CreateRESTRequest("PUT", container + "/" + blob + "?comp=metadata", string.Empty, headers).GetResponse() as HttpWebResponse;
                    response.Close();

                    return true;
                }
                catch (WebException ex)
                {
                    if (ex.Status == WebExceptionStatus.ProtocolError &&
                        ex.Response != null &&
                        (int)(ex.Response as HttpWebResponse).StatusCode == 404)
                        return false;

                    throw;
                }
            });
        }
    }
}